package server;

import java.util.List;
import java.io.Serializable;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;

import database.DataHandler;

import server.database.ConnectionDatabase;
import server.tcp.ServerTCP;

import mockup.AppointmentMockup;
import mockup.InviteMockup;
import mockup.LoginMockup;
import mockup.MeetingMockup;
import mockup.MessageMockup;
import mockup.Mockup;
import mockup.ReservationMockup;
import mockup.RoomMockup;
import mockup.UserMockup;

/**
 * The server
 * @author daniel
 * @author Erik
 */
public class Server {

	public static boolean debug = false;
	public static boolean debugHigherLogic = false;
	public static boolean debugMiddleLogic = false;
	public static boolean debugLowerLogic = false;
	
	private static ServerTCP socket;

	//////////////////////////////////////////////////////////////
	// -----------------------   DATA   ------------------------//
	//////////////////////////////////////////////////////////////
	
	public static Map<Integer,UserMockup> users = new HashMap<Integer,UserMockup>();
	public static Map<Integer,AppointmentMockup> appointments = new HashMap<Integer,AppointmentMockup>();
	public static Map<Integer,MeetingMockup> meetings = new HashMap<Integer,MeetingMockup>();
	public static Map<Integer,ReservationMockup> reservations = new HashMap<Integer,ReservationMockup>();
	public static Map<Integer,MessageMockup> messages = new HashMap<Integer,MessageMockup>();
	public static Map<Integer,RoomMockup> rooms = new HashMap<Integer,RoomMockup>();
	
	public static final Map<Integer,Session> sessions = new HashMap<Integer, Session>();
	public static final Map<Integer, ArrayList<Mockup>> createUpdates = new HashMap<Integer, ArrayList<Mockup>>();
	public static final Map<Integer, ArrayList<Mockup>> deleteUpdates = new HashMap<Integer, ArrayList<Mockup>>();
	public static final Map<Integer, ArrayList<Mockup>> changeUpdates = new HashMap<Integer, ArrayList<Mockup>>();
	
	public static void emptyDatabase() {
		//TODO: REMOvE THiS COdE!!
		
		for(AppointmentMockup m : appointments.values())
			DataHandler.appointment.delete(m);
		for(MeetingMockup m : meetings.values())
			DataHandler.meeting.delete(m);
		for(ReservationMockup m : reservations.values())
			DataHandler.reservation.delete(m);
		for(MessageMockup m : messages.values())
			DataHandler.message.delete(m);
		
	}
	
	private static ArrayList<Mockup> getStartupMockups(UserMockup user) {
		ArrayList<Mockup> list = new ArrayList<Mockup>();
		list.addAll(users.values());
		list.addAll(rooms.values());
		
		for(AppointmentMockup app : appointments.values()) {
			if(app.getOwnerID() == user.getId())
				list.add(app);
		}
		
		HashMap<Integer,MeetingMockup> meetingMap = new HashMap<Integer, MeetingMockup>();
		for(MeetingMockup app : meetings.values()) {
			if(app.getOwnerID() == user.getId())
				meetingMap.put(app.getId(),app);
			else if(app.getParticipants().contains(user.getId()));
			meetingMap.put(app.getId(),app);
		}
		
		list.addAll(meetingMap.values());
		
		for(MessageMockup msg : messages.values()) {
			if(msg.getReceiver() == user.getId())
				list.add(msg);
			else if(msg instanceof InviteMockup) {
				InviteMockup invite = (InviteMockup)msg;
				int meetingId = invite.getMeeting();
				if(!meetingMap.containsKey(meetingId))
					continue;
				if(meetingMap.get(meetingId).getOwnerID() == user.getId())
					list.add(msg);
				else if(meetingMap.get(meetingId).getParticipants().contains(user.getId()))
					list.add(msg);	 
			}
			
		}
		
		return list;
	}
	
	public static boolean validate() {
		boolean result = true;
		for(Mockup m : users.values()) {
			if(m == null) {
				System.err.println("Found a null object in user list");
				result = false;
			}
		}
		for(Mockup m : appointments.values()) {
			if(m == null) {
				System.err.println("Found a null object in appointment list");
				result = false;				
			}
		}
		for(Mockup m : meetings.values()) {
			if(m == null) {
				System.err.println("Found a null object in meetings list");
				result = false;				
			}
		}
		for(Mockup m : reservations.values()) {
			if(m == null) {
				System.err.println("Found a null object in reservations list");
				result = false;				
			}
		}
		for(Mockup m : rooms.values()) {
			if(m == null) {
				System.err.println("Found a null object in room list");
				result = false;				
			}
		}
		
		return result;
	}

	public static void close() {
		ConnectionDatabase.close();
		closeNetwork();
		System.exit(0);
	}
	
	public static void closeNetwork() {
		System.out.println("Closing network");
		System.out.println("Disconnecting clients");
		for(Session s : sessions.values()) {
			s.getConnection().closeConnection();
		}
		while(Thread.activeCount() > 2 ) {
			try {
				Thread.sleep(1);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println("Forcing the server socket to close");
		socket.forceEnd();
		System.out.println("Network is closed");
	}

	/**
	 * Adds the {@link Session} to the active list
	 * @param e
	 */
	public static void addSession(Session s) {
		if(!sessions.containsKey(s.getSessionID())) {
			sessions.put(s.getSessionID(),s);
		}
	}

	/**
	 * Removes the {@link Session} from the active list
	 * @param e
	 */
	public static void removeSession(Session s) {
		if(sessions.containsKey(s.getSessionID())) {
			sessions.remove(s);
		}
	}

	public static ArrayList<RoomMockup> getAvailableRooms (ReservationMockup reservation) {
		return new ArrayList<RoomMockup>(rooms.values());
//		long end = reservation.getEndTime();
//		long start = reservation.getStartTime();
//		
//		Map<Integer,RoomMockup> temp = new HashMap<Integer, RoomMockup>();
//		temp.putAll(rooms);
//		
//		for (ReservationMockup res : reservations.values()) {
//			if(res.getRoomID() <= 0 || res.getId() == reservation.getId())
//				continue;
//			
//			long oStart = res.getStartTime();
//			long oEnd = res.getEndTime();
//			if((oStart <= end && oStart >= start) || (oEnd <= end && oEnd >= start) ||
//			(oStart <= start && oEnd >= end))
//				temp.remove(res.getRoomID());
//		}
//		
//		ArrayList<RoomMockup> available = new ArrayList<RoomMockup>();
//		available.addAll((Collection<? extends RoomMockup>) temp.values());
//		
//		return available;
	}

	public static Serializable getUsersAppointments(int id) {
		ArrayList<AppointmentMockup> list = new ArrayList<AppointmentMockup>();
		
		for(AppointmentMockup mockup : appointments.values()) {
			if(mockup.getOwnerID() == id)
				list.add(mockup);
		}
		return list;
	}
	
	public static Serializable getUsersMeetings(int id) {
		ArrayList<AppointmentMockup> list = new ArrayList<AppointmentMockup>();
		
		for(MeetingMockup mockup : meetings.values()) {
			if(mockup.getOwnerID() == id)
				list.add(mockup);
			else if(mockup.getParticipants().contains(id));
				list.add(mockup);
		}
		return list;
	}

	//////////////////////////////////////////////////////////////
	// -----------------------   MAIN   ------------------------//
	//////////////////////////////////////////////////////////////
	public static void main(String[] args) {
		int port = 28095;

		System.out.println(	
				"###########################################\n"+
				"###           Starting server           ###\n"+
				"###########################################\n");

		System.out.print("Enter port number(" + port + "): ");

		Scanner scanner = new Scanner(System.in);
		String line = scanner.nextLine();

		if(!line.equals("")) {
			try {
				port = Integer.parseInt(line);
			} catch (Exception e) {
				//do nothing, couldn't parse
			}
		}

		System.out.println("Opening database");
		ConnectionDatabase.connect();
		System.out.println();
		System.out.println("Reading from database");
		
		System.out.print("  Loading users... ");
		List<UserMockup> userList = DataHandler.user.getAll();
		for(UserMockup u : userList) {
			users.put(u.getId(), u);
		}
		System.out.println("\t\tfound " + users.size());
		
		System.out.print("  Loading appointments...");
		List<AppointmentMockup> appointmentList = DataHandler.appointment.getAll();
		for(AppointmentMockup u : appointmentList) {
			appointments.put(u.getId(), u);
		}
		System.out.println("\tfound " + appointments.size());
		
		System.out.print("  Loading meetings...");
		List<MeetingMockup> meetingtList = DataHandler.meeting.getAll();
		for(MeetingMockup u : meetingtList) {
			meetings.put(u.getId(), u);
		}
		System.out.println("\t\tfound " + meetings.size());
		
		System.out.print("  Loading reservations...");
		List<ReservationMockup> reservationList = DataHandler.reservation.getAll();
		for(ReservationMockup u : reservationList) {
			reservations.put(u.getId(), u);
		}
		System.out.println("\tfound " + reservations.size());
		
		System.out.print("  Loading message...");
		List<MessageMockup> messageList = DataHandler.message.getAll();
		for(MessageMockup u : messageList) {
			messages.put(u.getId(), u);
		}
		System.out.println("\t\tfound " + messages.size());

		
		System.out.print("  Loading rooms...");
		List<RoomMockup> roomList = DataHandler.room.getAll();
		for(RoomMockup u : roomList) {
			rooms.put(u.getId(), u);
		}
		System.out.println("\t\tfound " + rooms.size());
		
		System.out.println("Finished reading from database");
		System.out.println();
		
		System.out.println("Validating fetched objects...");
		System.out.println((!validate()) ? "Found errors!" : "Validation completed successfully");
		System.out.println();
		
		System.out.println("Starting server...");
		socket = new ServerTCP(port);
	    String ip = getIP();
	    if(ip == null)
	    	System.out.println("Started server on port " + port + ", ip-address is unknown");
	    else
	    	System.out.println("Started server on " + ip + ":" + port);
	    
		System.out.println();
		
		System.out.println("-------------------------------------------");
		
		System.out.println();
		
		while(true) {
			String input = scanner.nextLine();
			if(input.equalsIgnoreCase("exit") || input.equalsIgnoreCase("quit"))
				Server.close();
			else if(input.equals("clear")) {
				emptyDatabase();
				messages.clear();
				appointments.clear();
				meetings.clear();
				reservations.clear();
				System.out.println("Storage is cleared");
			}
			else if(input.equals("debug on")) {
				debug = true;
				debugHigherLogic = true;
				System.out.println("debug is activated");
			}
			else if(input.equals("debug off")) {
				debug = false;
				debugHigherLogic = false;
				debugLowerLogic = false;
				System.out.println("debug is deactivated");
			}
			else if(input.equals("validate"))
				System.out.println((validate()) ? "No errors found" : "");
		}
	}
	
	public static String getIP() {
		  Enumeration en;
		try {
			en = NetworkInterface.getNetworkInterfaces();
		    while (en.hasMoreElements()) {
		        NetworkInterface i = (NetworkInterface) en.nextElement();
		        for (Enumeration en2 = i.getInetAddresses(); en2.hasMoreElements();) {
		            InetAddress addr = (InetAddress) en2.nextElement();
		            if (!addr.isLoopbackAddress()) {
		                if (addr instanceof Inet4Address) {
		                    return addr.getHostAddress();
		                }
		                if (addr instanceof Inet6Address) {
		                        continue;
		                }
		            }
		        }
		    }
		} catch (SocketException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		    return null;
	}
	
	
	//////////////////////////////////////////////////////////////
	// ---------------   PACKAGE PROCESSING   ------------------//
	//////////////////////////////////////////////////////////////

	public static Serializable execute(Mockup input, Session session) {
		if(input instanceof LoginMockup) {
			LoginMockup login = (LoginMockup) input;
			
			if(login.getState() == Mockup.LOGOUT) {
				session.getConnection().closeConnection();
				createUpdates.remove(session.getUser().getId());
				changeUpdates.remove(session.getUser().getId());
				deleteUpdates.remove(session.getUser().getId());
			}
			LoginMockup checkedLogin = checkLogin(login, session);
			
			return checkedLogin;
		} else {
			if(debugMiddleLogic) System.out.println("Server::execute: got a " + 
					input.getClass().getSimpleName() + 
					((input instanceof Mockup) ? (" state="+((Mockup)input).getState() +
							" id=" +((Mockup)input).getId()): "" ));
			Serializable output = MockupProcessor.process(input, session);
			if(debugMiddleLogic) System.out.println("Server::execute: sending a " + 
					((output != null) ? output.getClass().getSimpleName() : "null object"));
			return output;
		}
	}

	/**
	 * Checks the login, and logs the user in if it is correct. It also logs out any other
	 * login for the same user if the login is successful.
	 * @param login The login mockup to process
	 * @param session The session the login mockup came from
	 * @return The correct login response for the client
	 */
	public static LoginMockup checkLogin(LoginMockup login, Session session) {
		
		UserMockup user = DataHandler.login.getUser(login.getUsername(), login.getPassword());
		if(user == null) {
			//failed login
			return new LoginMockup(-1, null);
		}

		//check for existing logins, if one is found, log them out
		for(Session s : sessions.values()) {
			if(s.getUser() != null && s.getUser().getId() == user.getId()) {
				s.setUser(null);
				s.getConnection().closeConnection();
			}
		}

		session.setUser(user);
		createUpdates.put(user.getId(), getStartupMockups(user));
		changeUpdates.put(user.getId(), new ArrayList<Mockup>());
		deleteUpdates.put(user.getId(), new ArrayList<Mockup>());

		if(debug)
			System.out.println("Server::checkLogin: logged in user " + user.getName() + 
				", there are now " + sessions.size() + " active sessions");
		return new LoginMockup(session.getSessionID(), user);
	}
}